features:
  - id: f1
    feature: Checkout requests the charge idempotently by cart id
    kind: step
    implemented: The charge call derives its idempotency key from the cart id, so a
      retry never double-charges — repo://storefront/src/pages/checkout.tsx ·
      api://payments/charges
    gap: —
    owner: "[Robin Cross](</Team/Robin Cross.md>)"
    tests: test://payments/charges.test.ts
    uuid: 1f546c77-95a9-44bd-8110-5fa9b238ab51
  - id: f2
    feature: A successful charge creates the orders row as `placed` and publishes
      order-placed
    kind: step
    implemented: The Payments Service writes the row and emits the event in the same
      handler — api://payments/charges · db://main/orders · event://order-placed
    gap: —
    owner: "[Robin Cross](</Team/Robin Cross.md>)"
    tests: test://storefront/checkout.e2e.ts
    uuid: b7a7b3f1-0746-403e-9a12-73370c27db07
  - id: f3
    feature: Every charge attempt is recorded in payment_attempts, declined ones
      included
    kind: rule
    implemented: —
    gap: Nothing documents the write on the decline path — the audit trail may have
      a hole there (see a1)
    owner: "[Robin Cross](</Team/Robin Cross.md>)"
    tests: —
    uuid: ca44003e-7d05-4ce3-86b7-ca0df6e93546
scenarios:
  - id: s1
    scenario: Charge succeeds on the first attempt
    kind: happy
    outcome: order `placed`, event published, buyer sees confirmation
    result: order placed
    tests: test://storefront/checkout.e2e.ts
    uuid: 66108f9a-4d7c-4560-ab3b-1f3a630d922e
  - id: s2
    scenario: Provider declines the card
    kind: unhappy
    outcome: no order row; buyer keeps the cart and can retry — see [Declined
      Payment Path](<Declined Payment Path.md>)
    result: no order
    tests: —
    uuid: c4830742-06d4-488d-85fa-252af6fe1d6d
  - id: s3
    scenario: Buyer retries after a timeout
    kind: edge
    outcome: idempotency key prevents a double charge; single order row
    result: order placed
    tests: test://payments/charges.test.ts
    uuid: e34558a3-f6ff-4cfd-9c21-2290aaa10587
anomalies:
  - a: a1
    anomaly: A declined charge leaves no record of the attempt
    about: s2
    why: The scenario says "no order row" and stops there, yet
      [payment_attempts](</Data/main/Tables/payment_attempts.md>) is documented
      as the audit trail money leaves behind — on this path nothing says the
      attempt is written, so a decline may be invisible to support
    status: open
    options: "Write the failed attempt (Recommended) :: record the decline in
      payment_attempts so support can see it; Document the existing behaviour ::
      the row is written and the scenario simply omits it; This is intended ::
      declines deliberately leave no trace"
    owner: "[Nadia Ilic](</Team/Nadia Ilic.md>)"
    uuid: 1f3aaf49-c6c9-48eb-9d6e-6c14f761bae7
    resolution: ""
open-questions:
  - q: q1
    question: After how many declines do we block further attempts?
    about: f1
    status: open
    options: "There is no limit today (Recommended) :: nothing in the charge path
      counts declines — decide whether one is needed before documenting it; A
      provider-side limit applies :: the provider throttles repeated declines
      and we rely on that — confirm and cite it"
    uuid: 0709ec35-52d2-4884-be35-e9d66006cbdf
    owner: ""
  - q: q2
    question: Does a retry after a timeout reuse the idempotency key or mint a new one?
    about: f1
    status: archived
    resolution: Reuses it — the key is derived from the cart id, so a timeout retry
      hits the same key and cannot double-charge. Folded into f1 and s3.
    uuid: 9eb0bf33-7125-4aec-a9d2-a7bd7f3d3fb4
